/*
 * Copyright (c) 2006 Your Corporation. All Rights Reserved.
 */

/**
 * Copyright 2007 Jason Horman, Pete Aykroyd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.functor.expression;

import org.util.ListUtils;

import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;


public abstract class AbstractJunction extends AbstractPredicateExpression implements Junction {
	private List<PredicateExpression> predicates;
	private transient Set<PredicateExpression> distinctPredicates;

	protected AbstractJunction(List<PredicateExpression> predicates) {
		this.predicates = predicates;
	}

	public boolean isEmpty() {
		return getPredicates().isEmpty();
	}

	public List<PredicateExpression> getPredicates() {
		return predicates;
	}

	public void add(PredicateExpression exp) {
		if (addHelper(exp)) return;
		modified();
	}

	/**
	 * Adds the expression to this, with two optimizations:
	 * 1) If we're calling addHelper(this), return true, which is a signal that we avoided creating a loop.
	 * 2) If we're adding an instance of this.getClass() to this, just pull out the predicates and add them
	 *    in order to avoid a possibly deeply nested expression.
	 */
	private boolean addHelper(PredicateExpression exp) {
		if (this == exp) return true;
		if (getClass().equals(exp.getClass())) {
			for (PredicateExpression newExp : ((Junction)exp).getPredicates())
				addHelper(newExp);
		} else {
			predicates.add(exp);
		}
		return false;
	}

	public void remove(PredicateExpression exp) {
		predicates.remove(exp);
		modified();
	}

	public PredicateExpression removeLast() {
		PredicateExpression removed = ListUtils.removeLast(predicates);
		modified();
		return removed;
	}

	public Set<PredicateExpression> getDistinctPredicates() {
		if (distinctPredicates == null)
			distinctPredicates = new HashSet(predicates);
		return distinctPredicates;
	}

	public boolean equals(Object o) {
		if (this == o) return true;
		if (o == null || getClass() != o.getClass()) return false;
		AbstractJunction that = (AbstractJunction) o;
		if (!getDistinctPredicates().equals(that.getDistinctPredicates())) return false;
		return true;
	}

	public int computeHashCode() {
		return predicates.hashCode();
	}

	public void toString(StringBuilder builder) {
		for (Iterator<PredicateExpression> iterator = predicates.iterator(); iterator.hasNext();) {
			PredicateExpression predicate = iterator.next();
			if (predicate instanceof Junction) {
				builder.append('(');
				builder.append(predicate);
				builder.append(')');
			} else {
				builder.append(predicate);
			}

			if (iterator.hasNext())
				builder.append(getJoiningKeyword());
		}
	}

	protected abstract String getJoiningKeyword();

	public void modified() {
		super.modified();
		distinctPredicates = null;
	}
}
